#include "pch/std_array.h"
#include "pch/std_cassert.h"
#include "pch/std_cmath.h"
#include "pch/std_algorithm.h"

#include "pch/Eigen.h"

#include "triangle.h"
#include "vector.h"

using namespace BaseGeo;


static inline double areaIn3Points(const Point& point_a, const Point& point_b, const Point& point_c)
{
    Vector vector_ab = Vector(point_a, point_b);
    Vector vector_ac = Vector(point_a, point_c);

    return fabs(CrossMult(vector_ab, vector_ac));
}


void Triangle::buildCircumCircle()
{
    auto square = [](double value) { return value * value; };
    double vertex_to_origin_length_square;
    Eigen::Matrix3d matrix_1, matrix_2, matrix_3;
    double det_1, det_2, det_3;

    for (int i = 0; i < TRIANGLE_VERTEX_NUMBER; i++) {
        vertex_to_origin_length_square = square(Distance(vertices[i], origin_point));
        matrix_1.row(i) = Eigen::Vector3d(vertex_to_origin_length_square, vertices[i].y, 1.0);
        matrix_2.row(i) = Eigen::Vector3d(vertices[i].x, vertex_to_origin_length_square, 1.0);
        matrix_3.row(i) = Eigen::Vector3d(vertices[i].x, vertices[i].y, 1.0);
    }

    det_1 = matrix_1.determinant();
    det_2 = matrix_2.determinant();
    det_3 = matrix_3.determinant();

    Point center = Point(det_1 / (2 * det_3), det_2 / (2 * det_3));
    double radius = Distance(center, vertices[0]);

    circumcircle.center = center;
    circumcircle.radius = radius;
}


bool Triangle::canBeTriangle(const Point& point_a, const Point& point_b, const Point& point_c) const
{
    const double epsilon = 1e-10;

    return areaIn3Points(point_a, point_b, point_c) > epsilon;
}


Triangle::Triangle() : vertices({Point(0.0, 0.0), Point(1.0, 0.0), Point(0.0, 1.0)}) {}


Triangle::Triangle(const Point& point_a, const Point& point_b, const Point& point_c)
{
    assert(canBeTriangle(point_a, point_b, point_c));

    this->vertices[0] = point_a;
    this->vertices[1] = point_b;
    this->vertices[2] = point_c;

    buildCircumCircle();
}


bool Triangle::IsPointInThis(const Point& point_x) const
{
    int i;
    std::array<double, TRIANGLE_VERTEX_NUMBER> cross_mult_values;

    for (i = 0; i < TRIANGLE_VERTEX_NUMBER; i++) {
        Vector to_point(vertices[i], point_x);
        Vector current(vertices[i], vertices[(i + 1) % 3]);

        cross_mult_values[i] = CrossMult(to_point, current);
    }

    return std::all_of(cross_mult_values.begin(), cross_mult_values.end(), [](double value){ return value <= 0.0;}) || 
           std::all_of(cross_mult_values.begin(), cross_mult_values.end(), [](double value){ return value >= 0.0;});
}


bool Triangle::IsPointInCircumCircle(const Point& point_x) const
{
    return circumcircle.IsPointInThis(point_x);
}


double Triangle::Area() const
{
    return areaIn3Points(vertices[0], vertices[1], vertices[2]);
}


const Circle& Triangle::GetCircumCircle() const
{
    return circumcircle;
}


std::ostream& operator<<(std::ostream& stream, const BaseGeo::Triangle& triangle)
{
    for (auto p : triangle.vertices) {
        stream << p;
    }

    return stream;
}